﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;
using System.IO;
using MentalAlchemy.Image;
using MentalAlchemy.Molecules.Image;

namespace MentalAlchemy.Compounds.Image
{
	public partial class ColorScaleControl : UserControl
	{
		private const string PALETTE_FILE_FILTER = "Osiris palette files (*.lut)|*.lut;*.LUT|All files (*.*)|*.*";

		private ColorScale cScale;
		private List<ColorScale> scales;
		private readonly ColorScale defaultScale = ColorScale.CreateDefaultColorScale();
		private List<Bitmap> scalesBmp;

		private IColorScaleOwner scaleOwner;

		//private float lowerBound;
		//private float upperBound;
		private const int boundWidth = 3;
		private bool changeUpperBound;
		private bool changeLowerBound;
		private const int MIN_PALETTE_SIZE = 64;

		#region - Public delegate. -
		public delegate void ColorScaleChanged(ColorScale cScale); 
		#endregion

		#region - Properties -
		public ColorScale Palette
		{
			set
			{
				if (value != null)
				{
					ApplyColorScale(value, false);
				}
			}
			get
			{
				return cScale;
			}
		}

		public IColorScaleOwner ScaleOwner
		{
			get { return scaleOwner;}
			set
			{
				if (value != null)
				{
					scaleOwner = value;
					if (scaleOwner.ColorScale != null)
					{
						Palette = scaleOwner.ColorScale;
					}
				}
			}
		}

		public ColorScaleChanged ColorScaleChangeHandler { get; set; }
		#endregion

		#region - Initialization. -
		public ColorScaleControl()
		{
			Init();
		}

		public void Init ()
		{
			InitializeComponent();
			ApplyColorScale(defaultScale, false);
		}

		/// <summary>
		/// Initialize color scales using the given path.
		/// </summary>
		/// <param name="path"></param>
		public void InitColorScales(string path)
		{
			if (!Directory.Exists(path)) { return; }

			scales = new List<ColorScale>();
			scalesBmp = new List<Bitmap>();

			const string EXT = ".lut";
			string[] files = Directory.GetFiles(path);
			int count = 0;
			foreach (string file in files)
			{
				// check file extension.
				string ext = Path.GetExtension(file);
				if (string.Compare(ext, EXT, true) == 0)
				{
					var scale = new ColorScale();
					scale.Load(file);
					scales.Add(scale);

					var scaleBmp = GetHorizontalPaletteBitmap(scale.Colors);
					scalesBmp.Add(scaleBmp);

					var item = new ToolStripMenuItem(scale.Name, scaleBmp);
					item.Tag = scale;
					item.Click += ColorScaleMenuItemClick;
					contextMenuStrip1.Items.Insert(count, item);
					++count;
				}
			}
		} 
		#endregion

		#region - Methods for drawing. -
		/// <summary>
		/// 
		/// </summary>
		/// <param name="colorScale"></param>
		/// <param name="updateControlOnly">Set to [True] only if color scale update is required without any affect to other controls.</param>
		public void ApplyColorScale(ColorScale colorScale, bool updateControlOnly)
		{
			if (colorScale == null)
			{
				colorScale = defaultScale;
			}

			cScale = colorScale;

			//
			// restore picture box position from the color scale bounds.
			var panelHeight = splitContainer1.Panel1.Height;
			var top = (int)(panelHeight * (ColorScale.PALETTE_SIZE - (float)colorScale.UpperBound) / ColorScale.PALETTE_SIZE);
			var bottom = (int)(panelHeight * (ColorScale.PALETTE_SIZE - (float)colorScale.LowerBound) / ColorScale.PALETTE_SIZE);
			PaletteBox.Top = top;
			PaletteBox.Height = bottom - top;

			//
			// get color scale bitmap.
			//var res = GetVerticalPaletteBitmap(colorScale.Colors);
			var res = GetVerticalPaletteBitmap(colorScale.InitColors, colorScale.IsInverted);

			// set fill colors for the split panel 1.
			PaletteBox.Image = res;
			splitContainer1.Panel1.Invalidate();

			// note: somehow I don't feel that it's right, because this method operates with array of colors, not with palette description.
			if (!updateControlOnly && ColorScaleChangeHandler != null && cScale != null) ColorScaleChangeHandler(colorScale);
		}
		#endregion

		#region - Methods for retrieval of color scales. -
		public static Bitmap GetHorizontalPaletteBitmap(Color[] colors)
		{
			var res = new Bitmap(ColorScale.PALETTE_SIZE, 10);

			// draw vertical lines correspondent to the given colors.
			for (int i = 0; i < ColorScale.PALETTE_SIZE; ++i)
			{
				for (int j = 0; j < 10; ++j)
				{
					res.SetPixel(i, j, colors[i]);
				}
			}
			return res;
		}

		public static Bitmap GetVerticalPaletteBitmap(Color[] colors, bool isInverted)
		{
			var res = new Bitmap(10, ColorScale.PALETTE_SIZE);

			// draw vertical lines correspondent to the given colors.
			for (int i = 0; i < 10; ++i)
			{
				if (!isInverted)
				{
					for (int j = 0; j < ColorScale.PALETTE_SIZE; ++j)
					{
						int col = ColorScale.PALETTE_SIZE - j - 1;
						res.SetPixel(i, col, colors[j]);
					}
				}
				else
				{
					for (int j = 0; j < ColorScale.PALETTE_SIZE; ++j)
					{
						res.SetPixel(i, j, colors[j]);
					}
				}
			}
			return res;
		} 
		#endregion

		#region - Color scale menu event handlers. -
		private void ColorScaleMenuItemClick(object sender, EventArgs e)
		{
			var item = (ToolStripMenuItem)sender;
			Palette = (ColorScale)item.Tag;
			SelectPalette();
		}

		private void loadToolStripMenuItem_Click(object sender, EventArgs e)
		{
			openFileDialog1.Filter = PALETTE_FILE_FILTER;
			var res = openFileDialog1.ShowDialog();

			if (res == DialogResult.OK)
			{
				var pDescr = new ColorScale();
				pDescr.Load(openFileDialog1.FileName);

				Palette = pDescr;
			}
		}

		private void SelectColorScaleBtn_Click(object sender, EventArgs e)
		{
			// show context menu.
			contextMenuStrip1.Show(Cursor.Position);
		}

		private void clearToolStripMenuItem_Click(object sender, EventArgs e)
		{
			ApplyColorScale(defaultScale, false);
			SelectPalette();
		}

		private void SelectPalette()
		{
			//
			// reset palette borders.
			ChangeColorScaleBounds(0, splitContainer1.Panel1.Height);
		}

		private void InvertBtn_Click(object sender, EventArgs e)
		{
			Palette.Invert();
			ApplyColorScale(Palette, false);

			if (scaleOwner != null)
			{
				scaleOwner.ColorScale = Palette;
				scaleOwner.InvalidateImage();
			}
		} 
		#endregion

		#region - Color scale bounds change. -
		private void PaletteBox_MouseMove(object sender, MouseEventArgs e)
		{
			if (changeUpperBound) MoveUpperBound(e.Location);
			else if (changeLowerBound) MoveLowerBound(e.Location);
			else
			{
				// check mouse coordinates.
				if (CursorInPaletteBounds(e.Location)) { Cursor = Cursors.SizeNS; }
				else { Cursor = Cursors.Arrow; }
				return;
			}

			//
			// Apply changed palette to the selected image.
			if (scaleOwner != null)
			{
				ChangeColorScaleBounds(PaletteBox.Top, PaletteBox.Bottom);
			}
		}

		private void ChangeColorScaleBounds (int top, int bottom)
		{
			PaletteBox.Top = top;
			PaletteBox.Height = bottom - top;

			splitContainer1_Panel1_Paint(this, null);
			if (ColorScaleChangeHandler != null && cScale != null)
			{
				//
				// calculate [lowerBound] and [upperBound] to fall into the range [0..256] ([0..ColorScale.PALETTE_SIZE])
				// note: [lowerBound] < [upperBound]
				// note: lower and upper bounds are scaled inverse of [bottom] and [top].
				cScale.LowerBound = (int)(ColorScale.PALETTE_SIZE * (splitContainer1.Panel1.Height - bottom + 1f) / splitContainer1.Panel1.Height);
				cScale.UpperBound = (int)(ColorScale.PALETTE_SIZE * (splitContainer1.Panel1.Height - top + 1f) / splitContainer1.Panel1.Height);

				var tempPalette = ColorScale.Rescale(cScale, cScale.LowerBound, cScale.UpperBound);
				//var tempPalette = new ColorScale(cScale);!!!
				ColorScaleChangeHandler(tempPalette);
			}
			//dcmImage.Palette = ColorScale.Rescale(palette, (int)lowerBound, (int)upperBound);
			//dcmImage.InvalidateImage();
		}

		private void PaletteBox_MouseLeave(object sender, EventArgs e)
		{
			Cursor = Cursors.Arrow;
			changeUpperBound = changeLowerBound = false;
		}

		private void PaletteBox_MouseDown(object sender, MouseEventArgs e)
		{
			if (!changeUpperBound && CursorInUpperPaletteBound(e.Location))
			{
				changeUpperBound = true;
			}
			else if (!changeLowerBound && CursorInLowerPaletteBound(e.Location))
			{
				changeLowerBound = true;
			}
		}

		private void PaletteBox_MouseUp(object sender, MouseEventArgs e)
		{
			changeUpperBound = changeLowerBound = false;
		}

		/// <summary>
		/// Note that coordinates of [dest] are relative to the PaletteBox!
		/// </summary>
		/// <param name="dest"></param>
		private void MoveUpperBound (Point dest)
		{
			var offset = dest.Y;
			if (PaletteBox.Top + offset <= 0)
			{
				offset = 1 - PaletteBox.Top;
			}

			if ((offset > 0 && PaletteBox.Height > MIN_PALETTE_SIZE)	// move down.
				||
				(offset < 0 && PaletteBox.Location.Y > 0))	// move up.
			{
				// 1. define new color scale size.
				int newHeight = Math.Max(PaletteBox.Height - offset, MIN_PALETTE_SIZE);
				newHeight = Math.Min(newHeight, splitContainer1.Panel1.Height);

				// 2. modify color scale box.
				PaletteBox.Location = new Point(0, PaletteBox.Location.Y + offset);
				PaletteBox.Height = newHeight;
			}
		}

		/// <summary>
		/// Note that coordinates of [dest] are relative to the PaletteBox!
		/// </summary>
		/// <param name="dest"></param>
		private void MoveLowerBound(Point dest)
		{
			var offset = dest.Y - PaletteBox.Height;
			if (PaletteBox.Bottom + offset >= splitContainer1.Panel1.Height)
			{
				offset = splitContainer1.Panel1.Height - PaletteBox.Bottom - 1;
			}

			if ((offset > 0 && PaletteBox.Bottom < splitContainer1.Panel1.Height)	// move down
				|| (offset < 0 && PaletteBox.Height > MIN_PALETTE_SIZE))	// move up
			{
				// validate height.
				var newHeight = Math.Min(PaletteBox.Height + offset, splitContainer1.Panel1.Height);
				newHeight = Math.Max(newHeight, MIN_PALETTE_SIZE);
				PaletteBox.Height = newHeight;
			}
		}
		#endregion

		#region - Check cursor location. -
		private bool CursorInPaletteBounds(Point loc)
		{
			return CursorInLowerPaletteBound(loc) || CursorInUpperPaletteBound(loc);
		}

		private bool CursorInUpperPaletteBound(Point loc)
		{
			return loc.Y < boundWidth;
		}

		private bool CursorInLowerPaletteBound(Point loc)
		{
			return loc.Y > PaletteBox.Height - 2*boundWidth;
		}
		#endregion

		private void splitContainer1_Panel1_Paint(object sender, PaintEventArgs e)
		{
			// color-fill the upper part of the palette container.
			var g = Graphics.FromHwnd(splitContainer1.Panel1.Handle);
			var pen = new Pen(Palette.GetFirstColor());
			g.FillRectangle(pen.Brush, 0, 0, splitContainer1.Panel1.Width, PaletteBox.Top);

			// color-fill the lower part of the palette container.
			pen.Color = Palette.GetLastColor();
			g.FillRectangle(pen.Brush, 0, PaletteBox.Bottom, splitContainer1.Panel1.Width, splitContainer1.Panel1.Height - PaletteBox.Bottom);

			// draw borders.
			pen.Color = Color.Green;
			pen.Width = boundWidth;
			g.DrawLine(pen, 0, PaletteBox.Top - 1, splitContainer1.Width, PaletteBox.Top - 1);	// upper bound.
			g.DrawLine(pen, 0, PaletteBox.Bottom + 1, splitContainer1.Width, PaletteBox.Bottom + 1);	// lower bound.
			g.Dispose();
		}
	}

	///// <summary>
	///// Class to represent color scales.
	///// </summary>
	//public class ColorScale
	//{
	//    #region - Private fields. -
	//    private const string DEFAULT_SCALENAME = "(Unnamed)";
	//    private bool isInverted;

	//    /// <summary>
	//    /// Array to store initial color scale colors.
	//    /// </summary>
	//    private Color[] initColors = new Color[PALETTE_SIZE];
	//    #endregion

	//    #region - Public fields & properties. -
	//    public const int PALETTE_SIZE = 256;
	//    public const string DEFAULT_NAME = "Grayscale";

	//    public string Name { get; set; }
	//    public Color[] Colors { get; set; }
	//    public Color[] InitColors
	//    {
	//        get { return initColors; }
	//    }

	//    /// <summary>
	//    /// Color scale upper bound.
	//    /// </summary>
	//    public int UpperBound { get; set; }

	//    /// <summary>
	//    /// Color scale lower bound.
	//    /// </summary>
	//    public int LowerBound { get; set; }

	//    public bool IsInverted { get { return isInverted; } }
	//    #endregion

	//    #region - Construction. -
	//    public ColorScale ()
	//    {
	//        Colors = new Color[PALETTE_SIZE];
	//        initColors = new Color[PALETTE_SIZE];

	//        UpperBound = PALETTE_SIZE;
	//        LowerBound = 0;
	//    }

	//    public ColorScale(ColorScale scale)
	//    {
	//        Colors = (Color[])scale.Colors.Clone();
	//        initColors = (Color[])scale.initColors.Clone();

	//        UpperBound = scale.UpperBound;
	//        LowerBound = scale.LowerBound;
	//    }
	//    #endregion

	//    #region - Public methods. -
	//    public void Load(string filename)
	//    {
	//        using (var reader = new StreamReader(filename))
	//        {
	//            char[] splitChars = { ' ', '\t' };
	//            Colors = new Color[PALETTE_SIZE];	// 
	//            //				var count = 0;

	//            Name = "";
	//            while (!reader.EndOfStream)
	//            {
	//                string line = reader.ReadLine();
	//                string[] comps = line.Split(splitChars);

	//                if (comps[0] == "*")
	//                {
	//                    for (int i = 1; i < comps.Length; ++i)
	//                    {
	//                        Name += comps[i] + " ";
	//                    }
	//                    continue;	// skip the header and description line.
	//                }
	//                if (comps[0] == "N")
	//                {
	//                    Name = "";	// reset previous name.
	//                    for (int i = 1; i < comps.Length; ++i)
	//                    {
	//                        Name += comps[i] + " ";
	//                    }
	//                    continue;	// skip the header and description line.
	//                }

	//                var count = int.Parse(comps[1]);
	//                if (Colors[count] == Color.Empty)
	//                {
	//                    int r = int.Parse(comps[2]), g = int.Parse(comps[3]), b = int.Parse(comps[4]);
	//                    if (r > ImageTools.Const.GRAYLEVEL_MAX) r /= ImageTools.Const.GRAYLEVELS_COUNT;
	//                    if (g > ImageTools.Const.GRAYLEVEL_MAX) g /= ImageTools.Const.GRAYLEVELS_COUNT;
	//                    if (b > ImageTools.Const.GRAYLEVEL_MAX) b /= ImageTools.Const.GRAYLEVELS_COUNT;
	//                    Colors[count] = Color.FromArgb(r, g, b);
	//                }
	//            }

	//            if (Name.Length == 0)
	//            {
	//                Name = DEFAULT_SCALENAME;
	//            }
	//        }

	//        //
	//        // save initial colors.
	//        Colors.CopyTo(initColors, 0);
	//    }

	//    public void Invert()
	//    {
	//        int count = Colors.Length - 1, count_2 = Colors.Length / 2;
	//        for (int i = 0; i < count_2; ++i)
	//        {
	//            var temp = Colors[i];
	//            Colors[i] = Colors[count - i];
	//            Colors[count - i] = temp;
	//        }
	//        isInverted = !isInverted;
	//    }

	//    public Color GetFirstColor()
	//    {
	//        return Colors[Colors.Length - 1];
	//    }

	//    public Color GetLastColor()
	//    {
	//        return Colors[0];
	//    }

	//    public Color GetFirstInitColor()
	//    {
	//        return initColors[Colors.Length - 1];
	//    }

	//    public Color GetLastInitColor()
	//    {
	//        return initColors[0];
	//    }

	//    public void CopyColors(Color[] colors)
	//    {
	//        colors.CopyTo(Colors, 0);
	//        colors.CopyTo(initColors, 0);
	//    }

	//    /// <summary>
	//    /// Restores colors of the given color scale (removes rescaling effect).
	//    /// </summary>
	//    public void Restore ()
	//    {
	//        initColors.CopyTo(Colors, 0);
	//    }
	//    #endregion

	//    #region - Public static methods. -
	//    public static ColorScale CreateDefaultColorScale()
	//    {
	//        var res = new ColorScale();
	//        res.Name = DEFAULT_NAME;
	//        res.initColors = new Color[PALETTE_SIZE];
	//        for (int i = 0; i < PALETTE_SIZE; ++i)
	//        {
	//            res.initColors[i] = Color.FromArgb(i, i, i);
	//        }
	//        res.initColors.CopyTo(res.Colors, 0);
	//        return res;
	//    } 

	//    /// <summary>
	//    /// Rescales given palette to fit into the interval of indices bounded by [lowerBound; upperBound).
	//    /// </summary>
	//    /// <param name="palette"></param>
	//    /// <param name="lowerBound"></param>
	//    /// <param name="upperBound"></param>
	//    /// <returns></returns>
	//    public static ColorScale Rescale(ColorScale palette, int lowerBound, int upperBound)
	//    {
	//        var res = new ColorScale();

	//        //res.Colors = new Color[palette.initColors.Length];

	//        //
	//        // set colors beyond the lower...
	//        var tempColors = new Color[palette.initColors.Length];
	//        for (var i = 0; i < lowerBound; ++i) tempColors[i] = palette.initColors[0];

	//        //
	//        // ...and the upper bounds.
	//        var lastColorIndex = palette.initColors.Length - 1;
	//        for (var i = Math.Max(upperBound, 0); i < res.initColors.Length; ++i) tempColors[i] = palette.initColors[lastColorIndex];

	//        //
	//        // fit colors from [palette] into [lowerBound; upperBound] interval of indices.
	//        var indexStep = (float)(palette.Colors.Length-1) / (upperBound - lowerBound);
	//        var idx = 0f;
	//        for (var i = Math.Max(lowerBound, 0); i < upperBound && i < tempColors.Length; i++, idx += indexStep)
	//        {
	//            tempColors[i] = palette.initColors[(int)idx];
	//        }

	//        res.CopyColors(tempColors);
	//        palette.initColors.CopyTo(res.initColors, 0);
	//        res.LowerBound = lowerBound;
	//        res.UpperBound = upperBound;
	//        return res;
	//    }

	//    /// <summary>
	//    /// Applies given color scale to the input image.
	//    /// </summary>
	//    /// <param name="img">Input image.</param>
	//    /// <param name="palette">Color scale.</param>
	//    /// <returns>Bitmap image after the color scale application.</returns>
	//    public static Bitmap ApplyChanges(Bitmap img, ColorScale palette)
	//    {
	//        var res = new Bitmap(img);
	//        var grayTable = MentalAlchemy.Molecules.Image.ImageConversion.ConvertToGrayscaleTable(res);
            
	//        int height = res.Height, width = res.Width;
	//        for (int i = 0; i < height; ++i)
	//        {
	//            for (int j = 0; j < width; ++j)
	//            {
	//                var idx = grayTable[i,j];
	//                var c = palette.Colors[idx];
	//                res.SetPixel(j, i, c);
	//            }
	//        }
	//        return res;
	//    }
	//    #endregion
	//}
}
